package org.moonlight.jvm.rtda.share.heap.symref;

import org.moonlight.jvm.classfile.constant.info.member.ConstantInterfaceMethodRefInfo;
import org.moonlight.jvm.rtda.share.heap.Class;
import org.moonlight.jvm.rtda.share.heap.classmember.Method;
import org.moonlight.jvm.rtda.share.methodarea.RtConstantPool;
import org.moonlight.jvm.utils.MethodLookupUtil;

/**
 * 接口方法的符号引用
 *
 * @author Moonlight
 * @createTime 2023/8/30 8:48
 **/
public class InterfaceMethodRef extends MemberRef {

    private Method method;

    public InterfaceMethodRef(RtConstantPool rtCp, ConstantInterfaceMethodRefInfo info) {
        super(rtCp, info);
    }

    /**
     * 获取接口方法
     *
     * @return Method
     * @createTime 10:29 2023/9/12
     * @author moonlight
     **/
    public Method resolvedInterfaceMethod() {
        if (this.method == null) {
            resolvedInterfaceMethodRef();
        }
        return this.method;
    }

    /**
     * 解析接口方法的符号引用，根据解析出来的方法符号引用信息，去查询对应的接口方法
     *
     * 如果类D想通过方法符号引用访问类C的某个方法，先要解析符号引用得到类C。
     * 如果C是接口，则抛出IncompatibleClassChangeError异常，否则根据方法名和描述符查找方法。
     * 如果找不到对应的方法，则抛出NoSuchMethodError异常，否则检查类D是否有权限访问该方法。
     * 如果没有，则抛出IllegalAccessError异常。
     *
     * @createTime 10:30 2023/9/12
     * @author moonlight
     **/
    private void resolvedInterfaceMethodRef() {
        Class d = this.rtCp.getClazz();
        Class c = this.resolvedClass();
        if (!c.isInterface()) {
            throw new IncompatibleClassChangeError();
        }
        Method m = lookupInterfaceMethod(c, this.name, this.descriptor);
        if (m == null) {
            throw new NoSuchMethodError();
        }
        if (!m.isAccessibleTo(d)) {
            throw new IllegalAccessError();
        }
        this.method = m;
    }

    private Method lookupInterfaceMethod(Class clazz, String methodName, String methodDesc) {
        for (Method m : clazz.getMethods()) {
            if (m.getName().equals(methodName) && m.getDescriptor().equals(methodDesc)) {
                return m;
            }
        }
        return MethodLookupUtil.lookupMethodInInterfaces(clazz.getInterfaces(), methodName, methodDesc);
    }

}